package cn.redragon.soa.config.web;

import static cn.redragon.soa.common.core.config.web.CommonObjectMapperConfig.generateObjectMapper;
import static cn.redragon.soa.common.core.config.web.CommonObjectMapperConfig.withXss;
import static cn.redragon.soa.common.core.constant.AuthConstant.EXPIRES_IN_HEADER;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;

import cn.redragon.soa.common.core.brand.BrandConverter;
import cn.redragon.soa.common.core.constant.Constants;
import cn.redragon.soa.common.core.dto.web.CustomLocalDateEditor;
import cn.redragon.soa.common.core.dto.web.CustomLocalDateTimeEditor;
import cn.redragon.soa.common.core.web.StringEscapeEditor;
import cn.redragon.soa.common.web.config.security.CommonCookieProcessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.validation.Validator;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@ConditionalOnWebApplication
public class WebMvcConfig implements WebMvcConfigurer {

    @Value("#{'${security.cors.allowed-origins:*}'.split(',')}")
    private List<String> corsAllowedOrigins;

    @Bean("commonJacksonHttpMessageConverter")
    public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
        ObjectMapper objectMapper = withXss(generateObjectMapper());
        return new MappingJackson2HttpMessageConverter(objectMapper);
    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setDateTimeFormatter(DateTimeFormatter.ofPattern(Constants.DateTimePattern.FMT_DATE_TIME));
        registrar.setDateFormatter(DateTimeFormatter.ofPattern(Constants.DateTimePattern.FMT_DATE));
        registrar.registerFormatters(registry);
        registry.addConverter(new BrandConverter());
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        String[] origins = getCorsAllowedOrigins().stream().map(String::trim).toArray(String[]::new);

        registry.addMapping("/**")
            .allowedOrigins(origins)
            .allowedMethods("GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
            .exposedHeaders(AUTHORIZATION, EXPIRES_IN_HEADER)
            .allowCredentials(false).maxAge(3600);
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
        resolver.setPageParameterName("page");
        resolver.setSizeParameterName("size");
        resolver.setOneIndexedParameters(true);
        resolver.setFallbackPageable(PageRequest.of(1, 10));
        argumentResolvers.add(resolver);
    }

    @Bean
    public ConfigurableWebBindingInitializer configurableWebBindingInitializer(
        FormattingConversionService mvcConversionService, Validator mvcValidator) {
        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
        initializer.setConversionService(mvcConversionService);
        initializer.setValidator(mvcValidator);
        initializer.setPropertyEditorRegistrar(propertyEditorRegistry -> {
            propertyEditorRegistry.registerCustomEditor(LocalDateTime.class,
                                                        new CustomLocalDateTimeEditor(DateTimeFormatter.ofPattern(Constants.DateTimePattern.FMT_DATE_TIME)));
            propertyEditorRegistry.registerCustomEditor(LocalDate.class,
                                                        new CustomLocalDateEditor(DateTimeFormatter.ofPattern(Constants.DateTimePattern.FMT_DATE)));
            propertyEditorRegistry.registerCustomEditor(String.class, new StringEscapeEditor());
        });

        return initializer;
    }

    public List<String> getCorsAllowedOrigins() {
        return corsAllowedOrigins;
    }

    @Bean
    public TomcatContextCustomizer sameSiteCookiesConfig(@Value("${server.servlet.session.cookie.secure:false}") boolean secure,
                                                         @Value("${server.servlet.session.cookie.http-only:false}") boolean httpOnly,
                                                         @Value("${server.servlet.session.cookie.same-site:None}") String sameSite) {
        return context -> {
            final CommonCookieProcessor cookieProcessor = new CommonCookieProcessor();
            cookieProcessor.setSameSiteCookies(sameSite);
            cookieProcessor.setAllowSecure(secure);
            cookieProcessor.setAllowHttpOnly(httpOnly);
            context.setCookieProcessor(cookieProcessor);
        };
    }

}
